package cn.enited.devtools.apiaggregator.api.invoke;

import cn.enited.devtools.apiaggregator.api.utils.SrvRequestUtil;
import cn.enited.devtools.apiaggregator.api.utils.SrvVariableUtil;
import cn.enited.devtools.apiaggregator.api.vo.ApiAnalysisObject;
import cn.enited.devtools.apiaggregator.api.vo.RemoteService;
import cn.enited.devtools.apiaggregator.consts.CommonConst;
import cn.enited.devtools.apiaggregator.entity.Project;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.internal.Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ApiInvoker {

    private static final long SRV_TASK_TIMEOUT = 30L;
    private static final String REQUEST_USER_AGENT = "User-Agent";
    private static final String REQUEST_CONTENT_TYPE = "Content-Type";


    private OkHttpClient okHttpClient;

    private ThreadPoolTaskExecutor apiAggExecutor;

    @Autowired
    public void setOkHttpClient(OkHttpClient okHttpClient) {
        this.okHttpClient = okHttpClient;
    }

    @Autowired
    public void setApiAggExecutor(ThreadPoolTaskExecutor apiAggExecutor) {
        this.apiAggExecutor = apiAggExecutor;
    }

    public Map<String, Object> invokeService(Project.Configuration pathConf, ApiAnalysisObject analysisObject,
                                             HttpServletRequest apiRequest, String apiRequestBodyJson) {

        Objects.requireNonNull(analysisObject, "接口分析对象不能为空");

        log.info("开始调用聚合接口{}.{}获取结果", analysisObject.getProjectName(), analysisObject.getName());
        StopWatch stopWatch = new StopWatch();
        stopWatch.start(StrUtil.format("{}-{}", analysisObject.getProjectName(), analysisObject.getName()));

        Map<String, Object> serviceResponse = new HashMap<>();

        final JSONObject apiRequestBody = StrUtil.isNotBlank(apiRequestBodyJson) ? JSON.parseObject(apiRequestBodyJson) : null;

        // 可并行执行的service
        if (CollectionUtil.isNotEmpty(analysisObject.getFreeService())) {
            final Map<String, CompletableFuture<Object>> tasks = new HashMap<>(analysisObject.getFreeService().size());
            analysisObject.getFreeService().forEach(srv -> {
                CompletableFuture<Object> srvTask = CompletableFuture.supplyAsync(() -> {
                    Request srvRequest = buildServiceRequest(pathConf, srv, apiRequest, apiRequestBody, serviceResponse);
                    return getServiceResponse(srvRequest);
                }, apiAggExecutor);
                tasks.put(srv.getId(), srvTask);
            });
            tasks.forEach((id, task) -> {
                try {
                    Object result = task.get(SRV_TASK_TIMEOUT, TimeUnit.SECONDS);
                    serviceResponse.put(id, result);
                } catch (Exception e) {
                    log.error(StrUtil.format("多线程调用Service[{}]时发生异常：", id), e);
                }
            });
            tasks.clear();
        }
        // 需串行执行的service
        if (CollectionUtil.isNotEmpty(analysisObject.getDependService())) {
            analysisObject.getDependService().forEach(srv -> {
                Request srvRequest = buildServiceRequest(pathConf, srv, apiRequest, apiRequestBody, serviceResponse);
                Object result = getServiceResponse(srvRequest);
                if (Objects.nonNull(result)) {
                    serviceResponse.put(srv.getId(), result);
                }
            });
        }

        stopWatch.stop();
        log.info("聚合接口{}${}完成调用，共耗时：{}秒", analysisObject.getProjectName(), analysisObject.getName(), stopWatch.getTotalTimeSeconds());
        log.info("======================================");
        return serviceResponse;
    }

    private String determineProjectPath(String serviceId, Project.Configuration pathConf) {
        if (MapUtil.isNotEmpty(pathConf.getSpecificPathPrefix()) && pathConf.getSpecificPathPrefix().containsKey(serviceId)) {
            return pathConf.getSpecificPathPrefix().get(serviceId);
        }
        return pathConf.getDefaultPathPrefix();
    }

    private Request buildServiceRequest(Project.Configuration pathConf, RemoteService service,
                                        HttpServletRequest apiRequest, JSONObject apiRequestBody,
                                        Map<String, Object> serviceResponse) {
        // 从apiRequestBody中获取的参数值
        // key: 全变量名
        // value: 变量值，基本类型、JSONObject或JSONArray
        Map<String, Object> varValues = SrvVariableUtil.extractParamValue(service, apiRequest, apiRequestBody, serviceResponse);
        // 获取service填充了参数值的相对路径
        String relativePath = SrvVariableUtil.formatServicePath(service, varValues);
        String projectPath = determineProjectPath(service.getId(), pathConf);
        String completableUrl = URLUtil.completeUrl(projectPath, relativePath);
        // 构建请求
        Request.Builder requestBuilder = new Request
                .Builder()
                .cacheControl(CacheControl.FORCE_NETWORK)
                .url(completableUrl);
        // 添加header
        requestBuilder.addHeader(REQUEST_USER_AGENT, apiRequest.getHeader(REQUEST_USER_AGENT));
        if (MapUtil.isNotEmpty(service.getHeaderParameters())) {
            service.getHeaderParameters().forEach((name, param) -> {
                requestBuilder.addHeader(name, SrvVariableUtil.getStringParamValue(param.getItem1(), varValues));
            });
        }
        // POST/PUT/DELETE请求添加request body或者form body
        if (SrvRequestUtil.requestBodyIsPossible(service)) {
            // 替换primitiveRequestBody原始对象中的参数变量值，生成实参
            JSONObject payload =  replacePayloadParamValue(service.getPrimitiveRequestBody(),
                    service.getRequestBodyParameters().keySet(), varValues);

            RequestBody requestBody = Util.EMPTY_REQUEST;
            if (SrvRequestUtil.requestBodyIsJson(service)) {
                requestBody = RequestBody.create(CommonConst.REQUEST_DATA_TYPE.JSON_MEDIA_TYPE, payload.toJSONString());
            } else {
                FormBody.Builder formBodyBuilder = new FormBody.Builder();
                payload.forEach((k, v) -> {
                    String strVal = StrUtil.EMPTY;
                    if (v instanceof Boolean) {
                        strVal = BooleanUtil.toStringTrueFalse((Boolean) v);
                    } else if (v instanceof String) {
                        strVal = (String) v;
                    } else if (v instanceof Number) {
                        strVal = StrUtil.str(v, CharsetUtil.CHARSET_UTF_8);
                    } else {
                        strVal = JSON.toJSONString(v);
                    }
                    formBodyBuilder.add(k, strVal);
                });
                requestBody = formBodyBuilder.build();
            }
            if (SrvRequestUtil.isPutRequest(service)) {
                requestBuilder.put(requestBody);
            } else if (SrvRequestUtil.isDeleteRequest(service)) {
                requestBuilder.delete(requestBody);
            } else {
                requestBuilder.post(requestBody);
            }
        } else if (SrvRequestUtil.isHeadRequest(service)) {
            requestBuilder.head();
        } else {
            requestBuilder.get();
        }
        return requestBuilder.build();
    }

    private JSONObject replacePayloadParamValue(JSONObject payload, Collection<String> varList, Map<String, Object> varValues) {
        String payloadJson = payload.toJSONString();
        for (String var : varList) {
            String val = CommonConst.NULL;
            Object vv = varValues.get(var);
            if (vv instanceof Number) {
                val = StrUtil.str(vv, CharsetUtil.CHARSET_UTF_8);
            } else if (vv instanceof Boolean) {
                val = BooleanUtil.toStringTrueFalse((Boolean) vv);
            } else if (vv instanceof String) {
                val = String.format("\"%s\"", vv);
            } else {
                val = JSON.toJSONString(vv);
            }
            String escapeVar = StrUtil.replace(var, SrvVariableUtil.VAR_TOKEN_ARRAY, SrvVariableUtil.VAR_TOKEN_ARRAY_ESCAPED);
            String regex = String.format("\\{%s\\}", escapeVar);
            payloadJson = ReUtil.replaceAll(payloadJson, regex, val);
        }
        return JSON.parseObject(payloadJson);
    }

    private Object getServiceResponse(Request srvRequest) {
        Object result = null;
        try (Response response = okHttpClient.newCall(srvRequest).execute()) {
            if (response.code() == HttpStatus.HTTP_OK) {
                if (Objects.isNull(response.body())) {
                    log.error("okhttp is ok but response body is null.");
                    return null;
                }
                String responseText = response.body().string();
                if (StrUtil.isNotBlank(responseText) && !StrUtil.equalsIgnoreCase(CommonConst.NULL, responseText)) {
                    if (JSONUtil.isJsonArray(responseText)) {
                        result = JSON.parseArray(responseText);
                    } else if (JSONUtil.isJsonObj(responseText)) {
                        result = JSON.parseObject(responseText);
                    } else if (NumberUtil.isInteger(responseText)) {
                        result = NumberUtil.parseInt(responseText);
                    } else if (NumberUtil.isLong(responseText)) {
                        result = NumberUtil.parseLong(responseText);
                    } else if (NumberUtil.isDouble(responseText)) {
                        result = NumberUtil.parseDouble(responseText);
                    } else if (StrUtil.equalsAny(responseText, true, CommonConst.BOOL.TRUE, CommonConst.BOOL.FALSE)) {
                        result = Boolean.valueOf(responseText);
                    } else {
                        result = responseText;
                    }
                }
            } else {
                log.error("okhttp got {} status code", response.code());
            }
        } catch (Exception e) {
            log.error("okhttp got error: ", e);
        }
        return result;
    }
}
